-- 2022-8-11

---[[ 15.2.1 保存不带循环的表

    -- 接下来更难一点的需求是保存表 保存表有几种方法 选用哪种方法取决于对具体表结构的假设 但没有一种算法适用于所有的情况
    -- 对于简答的表来说 不仅可以使用更简单的算法 而且输出也会更简洁和清晰

    -- 不使用循环序列化表
    function serialize(o)
        local t = type(o)
        if t == "number" or t == "string" or t == "boolean" or t == "nil" then
            io.write(string.format("%q",o))
        elseif t == "table" then
            io.write("\n")
            for k, v in pairs(o) do
                io.write("  ",k," = ")
                serialize(v)
                io.write(",\n")
            end
            io.write("}\n")
        else
            error("cannot serialize a ".. type(o))
        end
    end
    -- 尽管这个函数很简单 但它却可以合理的满足需求 只要表结构是一棵树(即没有共享的子表和环) 那么该函数甚至能处理嵌套的表(即表中还有其他的表)
    -- (在暑促在以缩进形式输出嵌套表看上去会更具美感)

    -- 上例中的函数假设了表中所有键都是合法的标识符 如果一个表的键是数字或者不是合法的Lua标识符 那么就会有问题
    -- 解决该问题的一种简单方式是1像下列代码一样处理每一个键
    io.write(string.format(" [%s] = ",serialize(k)))
    -- 经过这样的修改后 我们提高了该函数的健壮性 但却牺牲了文件结果的美观性 考虑如下的调用
    serialize{a = 12 , b = 'Lua' , key ='another "one"'}
    -- 第一版的函数serialize会输出:
    -- {
    --     a = 12,
    --     b = "Lua",
    --     key = "another \"one\"",
    -- }
    -- 与之对比 第二版的函数serialize则会输出
    -- {
    --     ["a"] = 12,
    --     ["b"] = "Lua",
    --     ["key"] = "another \"one\"",
    -- }
    
    -- 通过每个键是否需要方括号 可以在健壮性和美观性之间得到平衡 同样 我们将此实现留做练习
--]]

---[[ 15.2.2 保存带有循环的表

    -- 由于表构造器不能创建带循环的或共享子表的表 所以如果要处理表示通用拓扑结构(例如带循环或共享子表)的表 就需要采用不同的方法
    -- 我们需要引入名称来表示循环 因此 下面的函数把值外加其名称一起作为参数
    -- 另外 还必须使用一个额外的表来存储已保存表的名称 以便在发现循环时对其进行复用
    -- 这个额外的表使用此前已被保存的表作为键 以表的名称作为值

    -- 保存带有循环的表
    function basicSerialize(o)
        -- 假设'o'是一个数字或字符串
        return string.format("%q",o)
    end
    function save(name,value,saved)
        saved = saved or {} -- 初始值
        io.write(name," = ")
        if type(value) == "number" or type(value) == "string" then
            io.write(basicSerialize(value),"\n")
        elseif type(value) == "table" then
            if saved[value] then -- 值是否已被保存
                io.write(saved[value],"\n")  -- 使用之前的名称
            else
                saved[value] = name -- 保存名称供后续使用
                io.write("()\n")     -- 创建新表
                for k, v in pairs(value) do -- 保存表的字段
                    k = basicSerialize(k)
                    local fname = string.format("%s[%s]",name,k)
                    save(fname,v,saved)
                end
            end
        else
            error("cannot save a " .. type(value))
        end
    end
    -- 我们假设要序列化的表只使用字符串或数值作为键 函数basicSerialize用于对这些基本类型进行序列化并返回序列化后的结果
    -- 另一个函数save则完成具体的工作 其参数saved就是之前所说的用于存储已保存表的表
    -- 例如 假设要创建一个如下所示的表:
    local a = {x=1,y=2;{3,4,5}}
    a[2] = a -- 循环
    a.z = a[1] -- 共享子表
    -- 调用save("a",a)会将其保存为
    a = {}
    a[1] = {}
    a[1][1] = 3
    a[1][2] = 4
    a[1][3] = 5
    a[2] = a
    a["y"] = 2
    a["x"] = 1
    a["z"] = a[1]
    -- 取决于表的遍历情况 这些赋值语句实际执行顺序可能会有所不同 不过尽管如此 上述算法能够保证任何新定义节点中所用到的节点都是已经被定义过的
    -- 如果想保存具有共享部分的几个表 那么可以在调用函数save时使用相同的表saved函数 例如 假设有如下两个表
    a = {{"one","two"},3}
    b = {k = a[1]}
    -- 如果以独立的方式保存这些表 那么结果中不会有共同的部分
    -- 不过 如果调用save函数时使同一个表saved 那么结果就会共享共同的部分
    local t = {}
    save("a",a,t)
    save("b",b,t)
    --> a = {}
    --> a[1] = {}
    --> a[1][1] = "one"
    --> a[1][2] = "two"
    --> a[2] = 3
    --> b = {}
    --> b["k"] = a[1]

    -- 在Lua语言中 还有一些常见的方法
    -- 例如 我们可以在保存一个值时不指定全局名称而是通过一段代码来创建一个局部值将其返回 也可以在可能的时候使用列表的语法等等
    -- Lua语言给我们提供构建这些机制的工具
--]]